// Copyright Epic Games, Inc. All Rights Reserved.

#include "sentryintegration.h"

#include <zencore/config.h>
#include <zencore/logging.h>

#include <stdarg.h>
#include <stdio.h>

#if ZEN_PLATFORM_LINUX
#	include <pwd.h>
#endif

#if ZEN_PLATFORM_MAC
#	include <pwd.h>
#endif

ZEN_THIRD_PARTY_INCLUDES_START
#include <spdlog/spdlog.h>
ZEN_THIRD_PARTY_INCLUDES_END

#if ZEN_USE_SENTRY
#	define SENTRY_BUILD_STATIC 1
ZEN_THIRD_PARTY_INCLUDES_START
#	include <sentry.h>
#	include <spdlog/sinks/base_sink.h>
ZEN_THIRD_PARTY_INCLUDES_END

namespace sentry {

struct SentryAssertImpl : zen::AssertImpl
{
	virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
	OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack) override;
};

class sentry_sink final : public spdlog::sinks::base_sink<spdlog::details::null_mutex>
{
public:
	sentry_sink();
	~sentry_sink();

protected:
	void sink_it_(const spdlog::details::log_msg& msg) override;
	void flush_() override;
};

//////////////////////////////////////////////////////////////////////////

static constexpr sentry_level_t MapToSentryLevel[spdlog::level::level_enum::n_levels] = {SENTRY_LEVEL_DEBUG,
																						 SENTRY_LEVEL_DEBUG,
																						 SENTRY_LEVEL_INFO,
																						 SENTRY_LEVEL_WARNING,
																						 SENTRY_LEVEL_ERROR,
																						 SENTRY_LEVEL_FATAL,
																						 SENTRY_LEVEL_DEBUG};

sentry_sink::sentry_sink()
{
}
sentry_sink::~sentry_sink()
{
}

void
sentry_sink::sink_it_(const spdlog::details::log_msg& msg)
{
	if (msg.level != spdlog::level::err && msg.level != spdlog::level::critical)
	{
		return;
	}
	try
	{
		std::string	   Message = fmt::format("{}\n{}({}) [{}]", msg.payload, msg.source.filename, msg.source.line, msg.source.funcname);
		sentry_value_t event   = sentry_value_new_message_event(
			  /*   level */ MapToSentryLevel[msg.level],
			  /*  logger */ nullptr,
			  /* message */ Message.c_str());
		sentry_event_value_add_stacktrace(event, NULL, 0);
		sentry_capture_event(event);
	}
	catch (const std::exception&)
	{
		// If our logging with Message formatting fails we do a non-allocating version and just post the msg.payload raw
		char   TmpBuffer[256];
		size_t MaxCopy = zen::Min<size_t>(msg.payload.size(), size_t(255));
		memcpy(TmpBuffer, msg.payload.data(), MaxCopy);
		TmpBuffer[MaxCopy]	 = '\0';
		sentry_value_t event = sentry_value_new_message_event(
			/*   level */ SENTRY_LEVEL_ERROR,
			/*  logger */ nullptr,
			/* message */ TmpBuffer);
		sentry_event_value_add_stacktrace(event, NULL, 0);
		sentry_capture_event(event);
	}
}
void
sentry_sink::flush_()
{
}

void
SentryAssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack)
{
	// Sentry will provide its own callstack
	ZEN_UNUSED(Callstack);
	try
	{
		std::string	   Message = fmt::format("ASSERT {}:({}) [{}]\n\"{}\"", Filename, LineNumber, FunctionName, Msg);
		sentry_value_t event   = sentry_value_new_message_event(
			  /*   level */ SENTRY_LEVEL_ERROR,
			  /*  logger */ nullptr,
			  /* message */ Message.c_str());
		sentry_event_value_add_stacktrace(event, NULL, 0);
		sentry_capture_event(event);
	}
	catch (const std::exception&)
	{
		// If our logging with Message formatting fails we do a non-allocating version and just post the Msg raw
		sentry_value_t event = sentry_value_new_message_event(
			/*   level */ SENTRY_LEVEL_ERROR,
			/*  logger */ nullptr,
			/* message */ Msg);
		sentry_event_value_add_stacktrace(event, NULL, 0);
		sentry_capture_event(event);
	}
}

}  // namespace sentry

namespace zen {

#	if ZEN_USE_SENTRY
static void
SentryLogFunction(sentry_level_t Level, const char* Message, va_list Args, [[maybe_unused]] void* Userdata)
{
	char		LogMessageBuffer[160];
	std::string LogMessage;
	const char* MessagePtr = LogMessageBuffer;

	int n = vsnprintf(LogMessageBuffer, sizeof LogMessageBuffer, Message, Args);

	if (n >= int(sizeof LogMessageBuffer))
	{
		LogMessage.resize(n + 1);

		n = vsnprintf(LogMessage.data(), LogMessage.size(), Message, Args);

		MessagePtr = LogMessage.c_str();
	}

	switch (Level)
	{
		case SENTRY_LEVEL_DEBUG:
			ZEN_CONSOLE_DEBUG("sentry: {}", MessagePtr);
			break;

		case SENTRY_LEVEL_INFO:
			ZEN_CONSOLE_INFO("sentry: {}", MessagePtr);
			break;

		case SENTRY_LEVEL_WARNING:
			ZEN_CONSOLE_WARN("sentry: {}", MessagePtr);
			break;

		case SENTRY_LEVEL_ERROR:
			ZEN_CONSOLE_ERROR("sentry: {}", MessagePtr);
			break;

		case SENTRY_LEVEL_FATAL:
			ZEN_CONSOLE_CRITICAL("sentry: {}", MessagePtr);
			break;
	}
}
#	endif

SentryIntegration::SentryIntegration()
{
}

SentryIntegration::~SentryIntegration()
{
	if (m_IsInitialized && m_SentryErrorCode == 0)
	{
		logging::SetErrorLog("");
		m_SentryAssert.reset();
		sentry_close();
	}
}

void
SentryIntegration::Initialize(std::string SentryDatabasePath, std::string SentryAttachmentPath, bool AllowPII)
{
	m_AllowPII = AllowPII;

	if (SentryDatabasePath.starts_with("\\\\?\\"))
	{
		SentryDatabasePath = SentryDatabasePath.substr(4);
	}
	sentry_options_t* SentryOptions = sentry_options_new();
	sentry_options_set_dsn(SentryOptions, "https://8ba3441bebc941c1ae24b8cd2fd25d55@o10593.ingest.sentry.io/5919284");
	sentry_options_set_database_path(SentryOptions, SentryDatabasePath.c_str());
	sentry_options_set_logger(SentryOptions, SentryLogFunction, this);
	if (SentryAttachmentPath.starts_with("\\\\?\\"))
	{
		SentryAttachmentPath = SentryAttachmentPath.substr(4);
	}
	sentry_options_add_attachment(SentryOptions, SentryAttachmentPath.c_str());
	sentry_options_set_release(SentryOptions, ZEN_CFG_VERSION);

	// sentry_options_set_debug(SentryOptions, 1);

	m_SentryErrorCode = sentry_init(SentryOptions);

	if (m_SentryErrorCode == 0)
	{
		if (m_AllowPII)
		{
#	if ZEN_PLATFORM_WINDOWS
			CHAR  Buffer[511 + 1];
			DWORD BufferLength = sizeof(Buffer) / sizeof(CHAR);
			BOOL  OK		   = GetUserNameA(Buffer, &BufferLength);
			if (OK && BufferLength)
			{
				m_SentryUserName = std::string(Buffer, BufferLength - 1);
			}
			BufferLength = sizeof(Buffer) / sizeof(CHAR);
			OK			 = GetComputerNameA(Buffer, &BufferLength);
			if (OK && BufferLength)
			{
				m_SentryHostName = std::string(Buffer, BufferLength);
			}
			else
			{
				m_SentryHostName = "unknown";
			}
#	endif	// ZEN_PLATFORM_WINDOWS

#	if (ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC)
			uid_t		   uid = geteuid();
			struct passwd* pw  = getpwuid(uid);
			if (pw)
			{
				m_SentryUserName = std::string(pw->pw_name);
			}
			else
			{
				m_SentryUserName = "unknown";
			}
			char HostNameBuffer[1023 + 1];
			int	 err = gethostname(HostNameBuffer, sizeof(HostNameBuffer));
			if (err == 0)
			{
				m_SentryHostName = std::string(HostNameBuffer);
			}
			else
			{
				m_SentryHostName = "unknown";
			}
#	endif
			m_SentryId						= fmt::format("{}@{}", m_SentryUserName, m_SentryHostName);
			sentry_value_t SentryUserObject = sentry_value_new_object();
			sentry_value_set_by_key(SentryUserObject, "id", sentry_value_new_string(m_SentryId.c_str()));
			sentry_value_set_by_key(SentryUserObject, "username", sentry_value_new_string(m_SentryUserName.c_str()));
			sentry_value_set_by_key(SentryUserObject, "ip_address", sentry_value_new_string("{{auto}}"));
			sentry_set_user(SentryUserObject);
		}

		m_SentryLogger = spdlog::create<sentry::sentry_sink>("sentry");
		logging::SetErrorLog("sentry");

		m_SentryAssert = std::make_unique<sentry::SentryAssertImpl>();
	}

	m_IsInitialized = true;
}

void
SentryIntegration::LogStartupInformation()
{
#	if (ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC)
	uid_t		   uid = geteuid();
	struct passwd* pw  = getpwuid(uid);
	if (pw)
	{
		m_SentryUserName = std::string(pw->pw_name);
	}
	ZEN_INFO("Username: '{}'", m_SentryUserName);

	char HostNameBuffer[1023 + 1];
	int	 err = gethostname(HostNameBuffer, sizeof(HostNameBuffer));
	if (err == 0)
	{
		ZEN_INFO("Hostname: '{}'", HostNameBuffer);
	}
#	endif
	if (m_IsInitialized)
	{
		if (m_SentryErrorCode == 0)
		{
			if (m_AllowPII)
			{
				ZEN_INFO("sentry initialized, username: '{}', hostname: '{}', id: '{}'", m_SentryUserName, m_SentryHostName, m_SentryId);
			}
			else
			{
				ZEN_INFO("sentry initialized with anonymous reports");
			}
		}
		else
		{
			ZEN_WARN("sentry_init returned failure! (error code: {})", m_SentryErrorCode);
		}
	}
}

void
SentryIntegration::ClearCaches()
{
	sentry_clear_modulecache();
}

}  // namespace zen
#endif
